home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / bin / podebconf-report-po < prev    next >
Encoding:
Text File  |  2006-12-12  |  23.0 KB  |  886 lines

  1. #!/usr/bin/perl -w
  2.  
  3. # podebconf-report-po, Send outdated debconf PO files to the last translator
  4. # Copyright (C) 2004-2006 Fabio Tranchitella <kobold@kobold.it>
  5. #                         Denis Barbier <barbier@debian.org>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. # GNU Library General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. #
  21.  
  22. ## Release information
  23. my $PROGRAM = "podebconf-report-po";
  24. my $VERSION = "0.10";
  25.  
  26. ## Loaded modules, require libmail-sendmail-perl
  27. use strict;
  28. eval q{use Mail::Sendmail;};
  29. die "$PROGRAM: This program requires the libmail-sendmail-perl package.\n".
  30.     "$PROGRAM: Aborting!\n" if $@;
  31. my $no_zlib = 0;
  32. eval q{use Compress::Zlib;};
  33. if ($@) {
  34.     $no_zlib = 1;
  35.     eval q{ sub Compress::Zlib::memGzip { return shift; } };
  36. }
  37. my $no_encode = 0;
  38. eval q{use Encode;};
  39. if ($@) {
  40.     $no_encode = 1;
  41. }
  42. use MIME::Base64;
  43. use MIME::QuotedPrint;
  44. use Getopt::Long;
  45. use POSIX;
  46.  
  47. ## Global variables
  48. my $CONF_ARG;
  49. if (-e $ENV{'HOME'}."/.podebconf-report-po.conf") {
  50.     $CONF_ARG = $ENV{'HOME'}."/.podebconf-report-po.conf";
  51. }
  52. my $NO_CONF;
  53. my $HELP_ARG = 0;
  54. my $VERSION_ARG = 0;
  55. my $VERBOSE_ARG;
  56. my $NO_VERBOSE;
  57. my $SUBMIT_ARG = 0;
  58. my $FORCE_ARG;
  59. my $NO_FORCE;
  60. my $CALL;
  61. my $POTFILE = "";
  62. my $LANGS = "";
  63. my $LANGUAGETEAM_ARG;
  64. my $NO_LANGUAGETEAM;
  65. my $SMTP_ARG;
  66. my $TEMPLATE_ARG;
  67. my $NO_TEMPLATE;
  68. my $DEFAULT_ARG;
  69. my $NO_DEFAULT;
  70. my $PACKAGE_ARG = "";
  71. my $SUMMARY_ARG;
  72. my $NO_SUMMARY;
  73. my $FROM_ARG;
  74. my $BTS_ARG = "";
  75. my $DEADLINE_ARG = "";
  76. my $PODIR_ARG = "";
  77. my $GZIP_ARG;
  78. my $NO_GZIP;
  79. my $UTF8;
  80. my $NO_UTF8;
  81.  
  82. my @TOPDIRS = qw{../.. .. .};
  83.  
  84. my $PODIR = '';
  85.  
  86. my $EDITOR = '/usr/bin/sensible-editor';
  87.  
  88. ## Default templates
  89. my $comments = "# Lines beginning with a number sign are comments, they are removed when
  90. # sending mails.  If a line is composed of a # followed by a 'Name: Value'
  91. # pair, it is interpreted as a mail header field and is passed to your mail
  92. # transport agent.  You can edit/add/remove those header fields.";
  93.  
  94. my $SUBJECT_TRANSLATOR = "Please update debconf PO translation for the package <package_and_version>";
  95. my $BODY_TRANSLATOR = $comments. "
  96. # From: <from>
  97. # Subject: <subject>
  98. # Reply-To: <reply-to>
  99. #
  100. # This mail will be sent to the following people:
  101. # (you can update email addresses, remove a line or add/remove the cross
  102. # between the brackets)
  103. <filelist>
  104.  
  105. Hi,
  106.  
  107. You are noted as the last translator of the debconf translation for
  108. <package>. The English template has been changed, and now some messages
  109. are marked \"fuzzy\" in your translation or are missing.
  110. I would be grateful if you could take the time and update it.
  111. <reply>
  112. <deadline>
  113.  
  114. Thanks,
  115. ";
  116.  
  117. my $SUBJECT_SUBMIT = "debconf PO translations for the package <package> are outdated";
  118. my $BODY_SUBMIT = $comments. "
  119. # From: <from>
  120. # Subject: <subject>
  121.  
  122. Package: <package>
  123. Version: N/A
  124. Severity: wishlist
  125. Tags: l10n
  126.  
  127. The following debconf translations are outdated:
  128.   <filelist>
  129.  
  130. Translators, please send your translations to this bugreport.
  131. <deadline>
  132.  
  133. Thanks,
  134. ";
  135.  
  136. my $SUBJECT_CALL = "debconf PO translations for the package <package>";
  137. my $BODY_CALL = $comments. "
  138. # From: <from>
  139. # Subject: <subject>
  140. # Reply-To: <reply-to>
  141.  
  142. Dear Debian I18N people,
  143.  
  144. I would like to know if some of you would be interested in translating
  145. <package>.
  146.  
  147. <package> already includes <filelist>.
  148. So do not translate it to these languages (the translators will be
  149. contacted separately).
  150.  
  151. <statistics>
  152.  
  153. <reply>
  154. <deadline>
  155.  
  156. If you have read so far, please find the POT file in attachement.
  157.  
  158. Thanks in advance,
  159. ";
  160.  
  161. my $SUBJECT = '';
  162. my $BODY = '';
  163. #  Warnings may be deleted from screen when entering editor,
  164. #  so display them when it is closed.
  165. my $warn = '';
  166.  
  167. ## Handle options
  168. GetOptions
  169. (
  170.  "conf=s"          => \$CONF_ARG,
  171.  "noconf"          => \$NO_CONF,
  172.  "help"            => \$HELP_ARG,
  173.  "version"         => \$VERSION_ARG,
  174.  "v|verbose"       => \$VERBOSE_ARG,
  175.  "noverbose"       => \$NO_VERBOSE,
  176.  "f|force"         => \$FORCE_ARG,
  177.  "noforce"         => \$NO_FORCE,
  178.  "podir=s"         => \$PODIR_ARG,
  179.  "smtp=s"          => \$SMTP_ARG,
  180.  "template=s"      => \$TEMPLATE_ARG,
  181.  "notemplate"      => \$NO_TEMPLATE,
  182.  "default"         => \$DEFAULT_ARG,
  183.  "nodefault"       => \$NO_DEFAULT,
  184.  "gzip"            => \$GZIP_ARG,
  185.  "nogzip"          => \$NO_GZIP,
  186.  "langs=s"         => \$LANGS,
  187.  "languageteam"    => \$LANGUAGETEAM_ARG,
  188.  "nolanguageteam"  => \$NO_LANGUAGETEAM,
  189.  "package=s"       => \$PACKAGE_ARG,
  190.  "deadline=s"      => \$DEADLINE_ARG,
  191.  "call:s"          => \$CALL,
  192.  "potfile=s"       => \$POTFILE,
  193.  "summary"         => \$SUMMARY_ARG,
  194.  "nosummary"       => \$NO_SUMMARY,
  195.  "from=s"          => \$FROM_ARG,
  196.  "bts=s"           => \$BTS_ARG,
  197.  "submit"          => \$SUBMIT_ARG,
  198.  "utf8"            => \$UTF8,
  199.  "noutf8"          => \$NO_UTF8
  200.  ) or &Help_InvalidOption;
  201.  
  202. my $conf = "";
  203.  
  204. unless ($NO_CONF or !defined $CONF_ARG) {
  205.     open (CNF, "< $CONF_ARG")
  206.         or die ("Couldn't read $CONF_ARG: $!\nExiting!\n");
  207.     while (<CNF>) {
  208.         $conf .= $_;
  209.     }
  210.     close(CNF)
  211.         or die ("Couldn't close $CONF_ARG: $!\nExiting!\n");
  212.  
  213.     $conf =~ s/^\s*#.*$//m;
  214.     $conf =~ s/\s*$//m;
  215. }
  216.  
  217. if ($conf =~ m/^smtp\s*(?:\s|=)\s*(.*)$/m) {
  218.     $SMTP_ARG = $1;
  219. } elsif (!defined $SMTP_ARG) {
  220.     $SMTP_ARG = "";
  221. }
  222.  
  223. if ($conf =~ m/^from\s*(?:\s|=)\s*(.*)$/m) {
  224.     $FROM_ARG = $1;
  225. } elsif (!defined $FROM_ARG) {
  226.     $FROM_ARG = (exists($ENV{'DEBEMAIL'}) ? $ENV{'DEBEMAIL'} : "");
  227. }
  228.  
  229. if (defined $NO_VERBOSE) {
  230.     $VERBOSE_ARG = 0;
  231. } elsif ($conf =~ m/^verbose$/m) {
  232.     $VERBOSE_ARG = 1;
  233. } elsif (!defined $VERBOSE_ARG) {
  234.     $VERBOSE_ARG = 0;
  235. }
  236.  
  237. if (defined $NO_FORCE) {
  238.     $FORCE_ARG = 0;
  239. } elsif ($conf =~ m/^force$/m) {
  240.     $FORCE_ARG = 1;
  241. } elsif (!defined $FORCE_ARG) {
  242.     $FORCE_ARG = 0;
  243. }
  244.  
  245. if (defined $NO_TEMPLATE) {
  246.     $TEMPLATE_ARG = "";
  247. } elsif ($conf =~ m/^template\s*(?:\s|=)\s*(.*)$/m) {
  248.     $TEMPLATE_ARG = $1;
  249. } elsif (!defined $TEMPLATE_ARG) {
  250.     $TEMPLATE_ARG = "";
  251. }
  252.  
  253. if (defined $NO_DEFAULT) {
  254.     $DEFAULT_ARG = 0;
  255. } elsif ($conf =~ m/^default$/m) {
  256.     $DEFAULT_ARG = 1;
  257. } elsif (!defined $DEFAULT_ARG) {
  258.     $DEFAULT_ARG = 0;
  259. }
  260.  
  261. if (defined $NO_GZIP) {
  262.     $GZIP_ARG = 0;
  263. } elsif ($conf =~ m/^nogzip$/m) {
  264.     $GZIP_ARG = 1;
  265. } elsif (!defined $GZIP_ARG) {
  266.     $GZIP_ARG = 0;
  267. }
  268.  
  269. if (defined $NO_LANGUAGETEAM) {
  270.     $LANGUAGETEAM_ARG = 0;
  271. } elsif ($conf =~ m/^languageteam$/m) {
  272.     $LANGUAGETEAM_ARG = 1;
  273. } elsif (!defined $LANGUAGETEAM_ARG) {
  274.     $LANGUAGETEAM_ARG = 0;
  275. }
  276.  
  277. if (defined $NO_SUMMARY) {
  278.     $SUMMARY_ARG = 0;
  279. } elsif ($conf =~ m/^summary$/m) {
  280.     $SUMMARY_ARG = 1;
  281. } elsif (!defined $SUMMARY_ARG) {
  282.     $SUMMARY_ARG = 0;
  283. }
  284.  
  285. if (defined $NO_UTF8) {
  286.     $UTF8 = 0;
  287. } elsif ($conf =~ m/^utf8$/m) {
  288.     $UTF8 = 1;
  289. } elsif (!defined $UTF8) {
  290.     $UTF8 = 0;
  291. }
  292.  
  293. &Help_PrintVersion if $VERSION_ARG;
  294. &Help_PrintHelp    if $HELP_ARG;
  295.  
  296. if ($no_encode and $UTF8) {
  297.     $warn .= "--utf8 requires the Encode perl module.  ".
  298.              "Turning this option off.\n";
  299.     $UTF8 = 0;
  300. }
  301.  
  302. ## Try to find default editor
  303. $EDITOR = $ENV{'EDITOR'} if exists($ENV{'EDITOR'});
  304. $EDITOR = $ENV{'VISUAL'} if exists($ENV{'VISUAL'});
  305.  
  306. ## Try to locate the PO directory
  307. if ($PODIR_ARG eq "") {
  308.     foreach my $d (@TOPDIRS) {
  309.         $PODIR = "$d/debian/po" if (-d "$d/debian/po");
  310.     }
  311. } else {
  312.     $PODIR = $PODIR_ARG;
  313. }
  314. die "Directory po not found, exiting!\n" if $PODIR eq "";
  315. die "Wrong argument: $PODIR is not a directory!\n" unless -d $PODIR;
  316.  
  317. if ($no_zlib && $GZIP_ARG) {
  318.     $warn .= 
  319.       "Warning: This program requires the libcompress-zlib-perl package in order\n".
  320.       "         to support the --gzip flag, but it is not installed.\n".
  321.       "         PO files will not be compressed!\n\n";
  322.     $GZIP_ARG = 0;
  323. }
  324.  
  325. ## Try to find the maintainer e-mail address and the package name
  326.  
  327. #  Package version
  328. my $PKG_VERSION = "N/A";
  329. #  Expanded into "<package> <version>" if version is found, <package> otherwise
  330. my $PACKAGE_AND_VERSION = "";
  331. if ($PACKAGE_ARG =~ s/_(.*)//) {
  332.     $PKG_VERSION = $1;
  333. }
  334.  
  335. if ($PACKAGE_ARG eq "" or $FROM_ARG eq "") {
  336.     my $CONTROL = '';
  337.     foreach my $d (@TOPDIRS) {
  338.         $CONTROL = "$d/debian/control" if (-f "$d/debian/control");
  339.     }
  340.     if ($CONTROL eq '') {
  341.         foreach my $d (@TOPDIRS) {
  342.             $CONTROL = "$d/debian/control.in" if (-f "$d/debian/control.in");
  343.         }
  344.     }
  345.  
  346.     if (-f $CONTROL) {
  347.         ##  Only read the first stanza
  348.         local $/ = "\n\n";
  349.         open (CNTRL, "< $CONTROL")
  350.             or die "Unable to read $CONTROL: $!\n";
  351.         my $text = <CNTRL>;
  352.         close (CNTRL)
  353.             or die "Unable to close $CONTROL: $!\n";
  354.         if ($PACKAGE_ARG eq "" && $text =~ m/^Source: (.*)/m) {
  355.             $PACKAGE_ARG = $1;
  356.         }
  357.  
  358.         if ($FROM_ARG eq "" && $text =~ m/^Maintainer: (.*)/m) {
  359.             $FROM_ARG = $1;
  360.         }
  361.     }
  362. }
  363. if ($PKG_VERSION eq "N/A") {
  364.     my $CHANGELOG = '';
  365.     foreach my $d (@TOPDIRS) {
  366.         $CHANGELOG = "$d/debian/changelog" if (-f "$d/debian/changelog");
  367.     }
  368.     if (-f $CHANGELOG) {
  369.         #  Version information is not vital, do not abort
  370.         #  if it cannot be retrieved.
  371.         if (open (CHG, "< $CHANGELOG")) {
  372.             while (<CHG>) {
  373.                 if (m/^$PACKAGE_ARG\s+\((.*)\)\s/) {
  374.                     $PKG_VERSION = $1;
  375.                 }
  376.                 last if m/^ --/;
  377.             }
  378.         }
  379.         close (CHG);
  380.     }
  381. }
  382. $PACKAGE_AND_VERSION = $PACKAGE_ARG .
  383.     ($PKG_VERSION ne 'N/A' ? " ".$PKG_VERSION : "");
  384. Verbose("Package: $PACKAGE_ARG");
  385. Verbose("Version: $PKG_VERSION");
  386. Verbose("Maintainer: $FROM_ARG");
  387.  
  388. if ($DEADLINE_ARG ne "") {
  389.     $DEADLINE_ARG = "\nThe deadline for receiving the updated translation is $DEADLINE_ARG.";
  390. }
  391.  
  392. my $REPLY = '';
  393. if ($BTS_ARG =~ m/^\d+$/) {
  394.     $BTS_ARG .= "\@bugs.debian.org";
  395.     $REPLY = "Please respect the Reply-To: field and send your updated translation to\n$BTS_ARG.";
  396. } else {
  397.     $REPLY = "Please send the updated file to me, or submit it as a wishlist bug\nagainst <package>.";
  398. }
  399.  
  400. if ($SUBMIT_ARG) {
  401.     $BODY = $BODY_SUBMIT;
  402.     $SUBJECT = $SUBJECT_SUBMIT;
  403. } elsif (defined $CALL) {
  404.     $CALL="Debian Internationalization <debian-i18n\@lists.debian.org>"
  405.         unless length $CALL;
  406.     $BODY = $BODY_CALL;
  407.     $SUBJECT = $SUBJECT_CALL;
  408. } else {
  409.     $CALL="";
  410.     $BODY = $BODY_TRANSLATOR;
  411.     $SUBJECT = $SUBJECT_TRANSLATOR;
  412. }
  413.  
  414. ## Apply the values to the subject and to the body of the message
  415.  
  416. $SUBJECT =~ s/<package>/$PACKAGE_ARG/g;
  417. $SUBJECT =~ s/<version>/$PKG_VERSION/g;
  418. $SUBJECT =~ s/<package_and_version>/$PACKAGE_AND_VERSION/g;
  419. $BODY =~ s/<reply>/$REPLY/g;
  420. $BODY =~ s/<reply-to>/$BTS_ARG/g;
  421. $BODY =~ s/\n# Reply-To: \n/\n/;
  422. $BODY =~ s/<subject>/$SUBJECT/g;
  423. $BODY =~ s/<package>/$PACKAGE_ARG/g;
  424. $BODY =~ s/<version>/$PKG_VERSION/g;
  425. $BODY =~ s/<package_and_version>/$PACKAGE_AND_VERSION/g;
  426. $BODY =~ s/<from>/$FROM_ARG/g;
  427. $BODY =~ s/\n<deadline>/$DEADLINE_ARG/g;
  428.  
  429. ## Check every file with .po extension in $PODIR ...
  430. Verbose("Checking for PO files in $PODIR");
  431. opendir(DIR, $PODIR);
  432. my $poFiles = {};
  433. my $statistics = "language        translated     fuzzy     untranslated\n".
  434.                  "-----------------------------------------------------\n";
  435. if (length $CALL and $POTFILE eq "") {
  436.     foreach my $potFile (grep(/\.pot$/, readdir(DIR))) {
  437.         if (length $POTFILE) {
  438.             die "Too many pot file found.\n".
  439.                 "Please specify one with the --potfile option.\n";
  440.         }
  441.         $POTFILE = $potFile;
  442.     }
  443. closedir(DIR);
  444. opendir(DIR, $PODIR);
  445.     if (length $POTFILE) {
  446.         print "Using $POTFILE for the call for translation\n";
  447.     } else {
  448.         warn "No POT file found. You should specify one with the ".
  449.              "--potfile option, or specify in the mail how to ".
  450.              "retrieve it."
  451.     }
  452. }
  453. if (length $CALL) {
  454.     foreach my $poFile (grep(/\.po$/, readdir(DIR))) {
  455.         $poFiles->{$poFile} = {};
  456.         my $cmd = "LC_ALL=C /usr/bin/msgfmt -o /dev/null --stat $PODIR/$poFile 2>&1";
  457.         my $stats = qx/$cmd/;
  458.         chomp $stats;
  459.         my ($t, $f, $u) = ("", "", "");
  460.         my $lang = $poFile;
  461.         $lang =~ s/\.po$//;
  462.         if ($stats =~ s/^([0-9]+) translated message[s ,]*//) {
  463.             $t = $1;
  464.         }
  465.         if ($stats =~ s/^([0-9]+) fuzzy translation[s ,]*//) {
  466.             $f = $1;
  467.         }
  468.         if ($stats =~ s/^([0-9]+) untranslated message[s ,]*//) {
  469.             $u = $1;
  470.         }
  471.         $statistics .= sprintf("  %-10s%10s  %10s    %10s\n", $lang, $t, $f, $u);
  472.     }
  473.     $BODY =~ s/<statistics>\n/$statistics/g;
  474. } else {
  475. foreach my $poFile (grep(/\.po$/, readdir(DIR))) {
  476.     local $/ = "\n\n";
  477.     $poFiles->{$poFile} = {};
  478.     my $outdated = 0;
  479.     my $found_header = 0;
  480.     open (PO, "< $PODIR/$poFile")
  481.         or die "Unable to read $PODIR/$poFile: $!\n";
  482.     while (<PO>) {
  483.         if ($found_header == 0 && m/msgid ""\nmsgstr/s) {
  484.             $found_header = 1;
  485.             #  Concatenate lines
  486.             s/"\n"//g;
  487.             if (m/\\nLast-Translator: (.*?)\\n/ && $1 ne 'FULL NAME <EMAIL@ADDRESS>') {
  488.                 $poFiles->{$poFile}->{translator} = $1;
  489.             } else {
  490.                 $warn .= "Warning: $poFile:  Unable to determine last translator.  Skipping file!\n";
  491.                 last;
  492.             }
  493.             if (m/\\nContent-Type: [^;]*; charset=(.*?)\\n/) {
  494.                 $poFiles->{$poFile}->{charset} = $1;
  495.             } else {
  496.                 $warn .= "Warning: $poFile:  Unable to determine charset.  Skipping file!\n";
  497.                 last;
  498.             }
  499.             if ($LANGUAGETEAM_ARG && m/\\nLanguage-Team: (.*?)\\n/) {
  500.                 $poFiles->{$poFile}->{team} = $1
  501.                     if $1 ne 'LANGUAGE <LL@li.org>';
  502.             }
  503.             next;
  504.         }
  505.         #  Ignore outdated msgids
  506.         next unless m/^msgid /m;
  507.         #  Check for fuzzy or missing translations
  508.         s/\n+$//s;
  509.         if (m/^#, .*fuzzy/m or m/\nmsgstr ""$/s) {
  510.             $outdated = 1;
  511.             last;
  512.         }
  513.     }
  514.     if ($UTF8) {
  515.         Encode::from_to($poFiles->{$poFile}->{translator},
  516.                         $poFiles->{$poFile}->{charset},
  517.                         "UTF-8");
  518.         Encode::from_to($poFiles->{$poFile}->{team},
  519.                         $poFiles->{$poFile}->{charset},
  520.                         "UTF-8");
  521.     }
  522.     close (PO)
  523.         or die "Unable to close $PODIR/$poFile: $!\n";
  524.     delete $poFiles->{$poFile} unless $outdated;
  525. }
  526. closedir(DIR);
  527. if (keys %$poFiles) {
  528.     print "Outdated files: ".join(' ', keys %$poFiles)."\n";
  529. } else {
  530.     print "No outdated files\n";
  531.     exit(0);
  532. }
  533. }
  534.  
  535. my %langs=();
  536. foreach (split(",", $LANGS)) {
  537.     $langs{$_.".po"} = 1;
  538. }
  539.  
  540. my $filelist = '';
  541. if ($SUBMIT_ARG or length $CALL) {
  542.     $filelist = join(' ', sort keys %$poFiles)."\n";
  543. } else {
  544.     foreach my $poFile (sort keys %$poFiles) {
  545.         $filelist .= '### ';
  546.         $filelist .= '[' .((!%langs or $langs{$poFile})?'*':' '). '] ';
  547.         $filelist .= $poFile . ': ' . $poFiles->{$poFile}->{translator};
  548.         $filelist .= ', ' . $poFiles->{$poFile}->{team} if defined($poFiles->{$poFile}->{team});
  549.         $filelist .= "\n";
  550.     }
  551.     #  Remove non-ASCII characters
  552.     $filelist = DropNonASCII($filelist)
  553.         unless ($UTF8);
  554. }
  555. $filelist =~ s/\n$//s;
  556. $BODY =~ s/<filelist>/$filelist/g;
  557.  
  558. my %headers = ();
  559. if ($TEMPLATE_ARG ne "") {
  560.     $BODY = &ReadFile($TEMPLATE_ARG);
  561. }
  562.  
  563. OPEN_EDITOR:
  564. $BODY = &OpenEditor($EDITOR, $BODY) if not $DEFAULT_ARG;
  565.  
  566. %headers = &ParseHeaders($BODY);
  567. my %To = &ParseTo($BODY);
  568.  
  569. print STDERR $warn if $warn ne '';
  570.  
  571. my @mails = ();
  572. if ($SUBMIT_ARG) {
  573.     $BODY =~ s/<filelist>/$filelist/g;
  574.     my %mail = (
  575.         From => $FROM_ARG,
  576.         To => "maintonly\@bugs.debian.org",
  577.         Subject => $SUBJECT,
  578.         'X-Mail-Originator' => "$PROGRAM $VERSION"
  579.     );
  580.     $mail{body} = encode_qp(&RemoveHeaders($BODY));
  581.     @mails = (\%mail);
  582. } elsif (length $CALL) {
  583.     $BODY =~ s/<filelist>/$filelist/g;
  584.     $BODY =~ s/<statistics>/$statistics/g;
  585.     my %mail = (
  586.         From => $FROM_ARG,
  587.         To => $CALL,
  588.         Subject => $SUBJECT,
  589.         'X-Mail-Originator' => "$PROGRAM $VERSION"
  590.     );
  591.     my $ext = ($GZIP_ARG ? '.gz' : '');
  592.         my $file = $POTFILE;
  593.         my $content = &ReadFile($PODIR . "/" . $file);
  594.         $content = Compress::Zlib::memGzip($content) if $GZIP_ARG;
  595.         my $file_encoded = encode_base64($content);
  596.         my $contentType = ($GZIP_ARG ? "application/octet-stream": "text/x-gettext; name=\"$file\"; charset=\"US-ASCII\"");
  597.         my $boundary = "=" . time() . "=";
  598.         $mail{'content-type'} = "multipart/mixed; boundary=\"$boundary\"";
  599.         my $charset = $UTF8?"utf-8":"us-ascii";
  600.         my $body = &RemoveHeaders($BODY);
  601.         $mail{body} = <<_EOF_;
  602. --$boundary
  603. Content-Type: text/plain; charset="$charset"
  604. Content-Transfer-Encoding: quoted-printable
  605.  
  606. $body
  607.  
  608. --$boundary
  609. Content-Type: $contentType
  610. Content-Transfer-Encoding: base64
  611. Content-Disposition: attachment; filename="$file$ext"
  612.  
  613. $file_encoded
  614. --$boundary--
  615. _EOF_
  616.     @mails = (\%mail);
  617. } else {
  618.     my $body = encode_qp(&RemoveHeaders($BODY));
  619.     my $ext = ($GZIP_ARG ? '.gz' : '');
  620.     foreach my $file (keys %$poFiles) {
  621.         if (defined $To{$file}) {
  622.             my $content = &ReadFile($PODIR . "/" . $file);
  623.             $content = Compress::Zlib::memGzip($content) if $GZIP_ARG;
  624.             my $file_encoded = encode_base64($content);
  625.             my $contentType = ($GZIP_ARG ? "application/octet-stream" : "text/x-gettext; name=\"$file\"; charset=\"$poFiles->{$file}->{charset}\"");
  626.             my %mail = (
  627.                 From => $FROM_ARG,
  628.                 To => $To{$file},
  629.                 Subject => $SUBJECT,
  630.                 'X-Mail-Originator' => "$PROGRAM $VERSION"
  631.             );
  632.  
  633.             my $boundary = "=" . time() . "=";
  634.             $mail{'content-type'} = "multipart/mixed; boundary=\"$boundary\"";
  635.             my $charset = $UTF8?"utf-8":"us-ascii";
  636.             $mail{body} = <<_EOF_;
  637. --$boundary
  638. Content-Type: text/plain; charset="$charset"
  639. Content-Transfer-Encoding: quoted-printable
  640.  
  641. $body
  642.  
  643. --$boundary
  644. Content-Type: $contentType
  645. Content-Transfer-Encoding: base64
  646. Content-Disposition: attachment; filename="$file$ext"
  647.  
  648. $file_encoded
  649. --$boundary--
  650. _EOF_
  651.  
  652.             push(@mails, \%mail);
  653.         }
  654.     }
  655. }
  656.  
  657. #  Add mail headers and remove non-ASCII characters
  658. foreach my $refmail (@mails) {
  659.     foreach my $h (keys(%headers)) {
  660.         if ($UTF8) {
  661.             $refmail->{$h} = encode_qp($headers{$h});
  662.             $refmail->{$h} =~ s/=$//m;
  663.             $refmail->{$h} =~ s/(\S*=\S*)/=?utf-8?Q?$1?=/g;
  664.         } else {
  665.             $refmail->{$h} = &DropNonASCII($headers{$h});
  666.         }
  667.     }
  668.     foreach my $h (qw(From To Subject)) {
  669.         if ($UTF8) {
  670.             unless ($refmail->{$h} =~ m/=\?utf-8\?Q\?/) {
  671.                 $refmail->{$h} = encode_qp($refmail->{$h});
  672.                 $refmail->{$h} =~ s/=$//m;
  673.                 $refmail->{$h} =~ s/(\S*=\S*)/=?utf-8?Q?$1?=/g;
  674.             }
  675.         } else {
  676.             $refmail->{$h} = &DropNonASCII($refmail->{$h});
  677.         }
  678.     }
  679.     $refmail->{smtp} = $SMTP_ARG if ($SMTP_ARG ne '');
  680. }
  681.  
  682. if (!$FORCE_ARG) {
  683.     my $answers = ($DEFAULT_ARG)?"[y/N/?]":"[y/N/e/?]";
  684.     QUESTION:
  685.     if ($SUBMIT_ARG) {
  686.         print "Ready to send the bug report against the package $PACKAGE_ARG, are you sure? $answers ";
  687.     } elsif (length $CALL) {
  688.         print "Ready to send the call for translation to $CALL, are you sure? $answers ";
  689.     } else {
  690.         print "Ready to send the emails, are you sure? $answers ";
  691.     }
  692.     my $line = <>;
  693.     chop $line;
  694.     if ($line eq "e" or $line eq "E") {
  695.         goto OPEN_EDITOR unless ($DEFAULT_ARG);
  696.     } elsif ($line eq "?") {
  697.         print "y    send the mail(s).\n".
  698.               "?    display this help message.\n".
  699.               ($DEFAULT_ARG?"":"e    reopen the editor.\n").
  700.               "N    exit, without sending mails.\n";
  701.         goto QUESTION;
  702.     }
  703.     exit(0) if ($line ne "Y" and $line ne "y");
  704. }
  705.  
  706. #  Make Perl compiler quiet
  707. print $Mail::Sendmail::error . $Mail::Sendmail::error if 0;
  708. foreach my $mail (@mails) {
  709.     sendmail(%{$mail}) || print "Couldn't send the email: $Mail::Sendmail::error\n";
  710. }
  711. if ($SUMMARY_ARG) {
  712.     my %summary = (
  713.         From => $FROM_ARG,
  714.         To => $FROM_ARG,
  715.         Subject => $SUBJECT,
  716.         'X-Mail-Originator' => "$PROGRAM $VERSION"
  717.     );
  718.     $summary{body} = "List of outdated files:\n";
  719.     foreach my $poFile (sort keys %$poFiles) {
  720.         $summary{body} .= '  ' . $poFile . ': ' . $poFiles->{$poFile}->{translator};
  721.         $summary{body} .= ', ' . $poFiles->{$poFile}->{team} if defined($poFiles->{$poFile}->{team});
  722.         $summary{body} .= "\n";
  723.     }
  724.     $summary{body} .= "Translators received the mail below.\n\n";
  725.     $summary{body} .= encode_qp(&RemoveHeaders($BODY));
  726.     sendmail(%summary) || print "Couldn't send the email: $Mail::Sendmail::error\n";
  727. }
  728. exit(0);
  729.  
  730. ###############################################################################
  731.  
  732. sub OpenEditor
  733. {
  734.     my $editor = shift;
  735.     my $body = shift;
  736.     my $opts = "";
  737.     my $tmpnam = tmpnam();
  738.  
  739.     open (OUT, "> $tmpnam")
  740.         or die ("Couldn't write $tmpnam: $!\nExiting!\n");
  741.     print OUT $body;
  742.     close(OUT)
  743.         or die ("Couldn't close $tmpnam: $!\nExiting!\n");
  744.  
  745.     $opts = "-f" if ($editor eq "vim");
  746.     system("$editor $opts $tmpnam");
  747.  
  748.     $body = &ReadFile($tmpnam) if (-f $tmpnam);
  749.     unlink($tmpnam);
  750.  
  751.     return $body;
  752. }
  753.  
  754. sub ParseHeaders
  755. {
  756.     my $body = shift;
  757.     my %headers = ();
  758.  
  759.     while ($body =~ s/^#[ \t]*([^\n]*)\n//s) {
  760.         my $comment = $1;
  761.         if ($comment =~ m/^([a-zA-Z0-9_-]+):\s*([^\n]+)$/) {
  762.             $headers{$1} = $2;
  763.         }
  764.     }
  765.     return %headers;
  766. }
  767.  
  768. sub ParseTo
  769. {
  770.     my $body = shift;
  771.     my %To = ();
  772.  
  773.     while ($body =~ s/#[ \t]*([^\n]*)\n//s) {
  774.         my $comment = $1;
  775.         if ($comment =~ s/^##[ \t]*\[(?:\*|x)\][ \t]*([^:]*):[ \t]*([^\n]*)$//s) {
  776.             $To{$1} = $2;
  777.         }
  778.     }
  779.     return %To;
  780. }
  781.  
  782. sub RemoveHeaders
  783. {
  784.     my $body = shift;
  785.     #  First remove comments
  786.     1 while $body =~ s/^#[^\n]*\n//s;
  787.     #  Optional empty lines
  788.     $body =~ s/^\s+//s;
  789.     return $body;
  790. }
  791.  
  792. sub DropNonASCII {
  793.     my $text = shift;
  794.     $text =~ s/[\x80-\xff]/?/g;
  795.     return $text;
  796. }
  797.  
  798. sub ReadFile
  799. {
  800.     my $file = shift;
  801.     local $/ = undef;
  802.     open(FILE, "< $file")
  803.         or die ("Couldn't read $file: $!\nExiting!\n");
  804.     my $body = <FILE>;
  805.     close(FILE)
  806.         or die ("Couldn't close $file: $!\nExiting!\n");
  807.     return $body;
  808. }
  809.  
  810. ## Handle invalid arguments
  811. sub Help_InvalidOption
  812. {
  813.     print STDERR "Try `${PROGRAM} --help' for more information.\n";
  814.     exit 1;
  815. }
  816.  
  817. ## Print the usage message and exit
  818. sub Help_PrintHelp
  819. {
  820.     print <<_EOF_;
  821.  
  822. Usage: ${PROGRAM} [OPTIONS]
  823. Send outdated debconf PO files to the last translators.
  824.  
  825. Options:
  826.   --help                display this help and exit
  827.   --version             display version information and exit
  828.   -v, --verbose         display additional information
  829.   --noverbose
  830.   -f, --force           send the email without confirmation
  831.   --noforce
  832.   --utf8                send the mail in UTF-8
  833.   --noutf8
  834.   --podir=DIRECTORY     specify where are located the PO files
  835.   --smtp=SERVER         specify SMTP server for mailing (default localhost)
  836.   --template=TEMPLATE   specify file to use it as template for the emails
  837.   --notemplate
  838.   --default             don't open the editor and use the template as is
  839.   --nodefault
  840.   --gzip                compress PO files with gzip
  841.   --nogzip
  842.   --package=PACKAGE     specify the name of the package
  843.   --from=MAINTAINER     specify the name and the email address of the sender
  844.   --deadline=DEADLINE   specify the deadline for receiving the updated
  845.                         translations
  846.   --langs=LANGUAGES     restrict sending emails only to these languages
  847.   --languageteam        send the email also to the Language Team
  848.   --nolanguageteam
  849.   --summary             send a status report to the maintainer with the list
  850.                         of emails sent to translators
  851.   --nosummary
  852.   --submit              send a bug report against the package with a report
  853.                         of the outdated debconf translations
  854.   --bts=BUGNUMBER       specify the Debian bug number to set as reply-to
  855.   --call[=LIST]         send a call for translations to the LIST (or to
  856.                         the Debian I18N mailing list by default
  857.   --potfile=FILE        when used with --call, specifies the POT file to
  858.                         attach to the call for translations
  859.  
  860. _EOF_
  861.     exit 0;
  862. }
  863.  
  864. ## Print the version text and exit
  865. sub Help_PrintVersion
  866. {
  867.     print <<_EOF_;
  868. ${PROGRAM} $VERSION
  869. Copyright (C) 2004-2006 Fabio Tranchitella and Denis Barbier.
  870. This is free software; see the source for copying conditions.  There is NO
  871. warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  872. _EOF_
  873.     exit 0;
  874. }
  875.  
  876. sub Verbose
  877. {
  878.     my $msg = shift;
  879.     return unless $VERBOSE_ARG;
  880.     $msg =~ s/^/**${PROGRAM}: /mg;
  881.     print STDERR $msg."\n";
  882. }
  883.